#include <Calculation/CalcEngine.hpp>



const std::string CalcEngine::errorMessages[3] = {
		"Math error - result overflow",
		"Math error - result negative",
		"Math error - division through zero"
};



void CalcEngine::performCalculation(std::string& input, std::string& output){

	std::string decOutputStr;
	std::string hexOutputStr;
	std::string binOutputStr;

	uint32_t firstOperandInt;
	uint32_t secondOperandInt;
	uint32_t result;
	uint8_t statusCode;

	/* Find operator */
	size_t operatorPos = input.find_first_of("+-*/");

	/* Construct strings */
	std::string mathOperator(input.substr(operatorPos,1));
	std::string firstOperand(input.substr(0,operatorPos));
	std::string secondOperand(input.substr(operatorPos+1,input.length()-operatorPos-2));

	/* Parse both operands */
	firstOperandInt = parseOperand(firstOperand);
	secondOperandInt = parseOperand(secondOperand);

	/* Perform the calculation */
	if(!mathOperator.compare("+")){
		statusCode = performAddition(&result, firstOperandInt , secondOperandInt);
	}else if(!mathOperator.compare("-")){
		statusCode = performSubtraction(&result, firstOperandInt, secondOperandInt);
	}else if(!mathOperator.compare("*")){
		statusCode = performMultiplication(&result, firstOperandInt, secondOperandInt);
	}else{
		statusCode = performDivision(&result, firstOperandInt, secondOperandInt);
	}

	std::cout << "\tOperator:  " << mathOperator << std::endl;
	std::cout << "\tOperand 1: " << firstOperandInt << std::endl;
	std::cout << "\tOperand 2: " << secondOperandInt << std::endl;

	if(statusCode == NO_ERROR){

		/* Generate output string */
		generateDec(decOutputStr, result);
		generateHex(hexOutputStr, result);
		generateBin(binOutputStr, result);

		std::ostringstream oss;
		oss << decOutputStr << " " << hexOutputStr << " " << binOutputStr;
		output = oss.str();

		std::cout << "\tResult:    " << output << std::endl;

	}else{

		output = errorMessages[statusCode];

		std::cout << "\t" << output << std::endl;
	}
}


uint8_t CalcEngine::performAddition(uint32_t *result, uint32_t firstOperand, uint32_t secondOperand){

	uint64_t resultTmp = (uint64_t)firstOperand + (uint64_t)secondOperand;

	if(resultTmp > 0xFFFFFFFF){
		return OVERFLOW_ERROR;
	}else{
		*result = resultTmp;
		return NO_ERROR;
	}

}

uint8_t CalcEngine::performSubtraction(uint32_t *result, uint32_t firstOperand, uint32_t secondOperand){

	if(secondOperand > firstOperand){
		return NEGATIVE_ERROR;
	}else{
		*result = firstOperand-secondOperand;
		return NO_ERROR;
	}

}

uint8_t CalcEngine::performMultiplication(uint32_t *result, uint32_t firstOperand, uint32_t secondOperand){

	uint64_t resultTmp = (uint64_t)firstOperand * (uint64_t)secondOperand;

	if(resultTmp > 0xFFFFFFFF){
		return OVERFLOW_ERROR;
	}else{
		*result = resultTmp;
		return NO_ERROR;
	}

}

uint8_t CalcEngine::performDivision(uint32_t *result, uint32_t firstOperand, uint32_t secondOperand){

	if(secondOperand == 0){
		return ZERO_ERROR;
	}else{
		*result = firstOperand / secondOperand;
		return NO_ERROR;
	}

}


uint32_t CalcEngine::parseOperand(std::string &operand){

	/* Search for "0x" and "b" to decide  with which base we're dealing with */
	if(operand.find("0x") != std::string::npos){
		return parseHex(operand);
	}else if(operand.find("b") != std::string::npos){
		return parseBin(operand);
	}else{
		return parseDec(operand);
	}

}

uint32_t CalcEngine::parseHex(std::string &operand){
	uint32_t number;
	std::istringstream iss(operand);
	iss >> std::hex >> number;
	return number;
}

uint32_t CalcEngine::parseDec(std::string &operand){
	uint32_t number;
	std::istringstream iss(operand);
	iss >> number;
	return number;
}

uint32_t CalcEngine::parseBin(std::string &operand){

	int number = 0;
	int len = operand.length()-1;

	for(int i = 0; i<len;i++){
		/* Loop through every character and set the specific bit if it equals '1' */
		if(operand.at(i) == '1'){
			number |= (1<<(len-i-1));
		}
	}

	return number;
}


void CalcEngine::generateHex(std::string &str, uint32_t num){

	std::ostringstream oss;

	/*
	 * Set format to base 16
	 */
	oss << "0x" << std::hex << std::uppercase << num;

	str = oss.str();
}

void CalcEngine::generateDec(std::string &str, uint32_t num){

	std::ostringstream oss;

	oss << num;

	str = oss.str();
}

void CalcEngine::generateBin(std::string &str, uint32_t num){

	int i=31;
	char raw[34];

	/* Set the last character and null termination manually */
	raw[32] = 'b';
	raw[33] = 0;

	while(num){

		/* Loop through every bit and add 0x30 to ist value to get an ASCI sign */
		raw[i--] = (num & 0x01) + 0x30;
		/* Shift right to get the next bit */
		num = (num>>1);
	}

	/* Assign the C-String to the C++ string */
	str.assign(raw+i+1);

}
